<template>
  <ElCard class="app-container">
    <div style="margin-bottom: 20px">大文件上传</div>
    <!-- vue帮我编写一个按钮点击选择文件 -->
    <Upload :onChange="onChange" type="primary">选择上传的文件</Upload>
    <el-table :data="tableData" style="width: 100%">
      <el-table-column prop="md5" label="md5"></el-table-column>

      <el-table-column prop="name" label="文件名" />
      <el-table-column prop="size" label="大小" />
      <el-table-column prop="status" label="状态">
        <template v-slot="{ row }">
          <span v-if="row.status == 0">正在计算md5</span>
          <span v-if="row.status == 1">计算md5成功,请上传</span>

          <span v-if="row.status == 3">已上传</span>
          <span v-if="row.status == 5">等待上传</span>
          <el-progress
            v-if="row.status === 2"
            :text-inside="true"
            :stroke-width="20"
            :percentage="parseFloat(row.percentage.toFixed(0))"
          ></el-progress>
        </template>
      </el-table-column>
      <el-table-column prop="option" label="操作">
        <template #default="{ row }">
          <el-button type="primary" size="small" @click="del(row)">
            删除
          </el-button>
        </template>
      </el-table-column>
    </el-table>
    <ElButton @click="upload" style="margin-top: 20px" type="primary">
      开始上传
    </ElButton>
  </ElCard>
</template>

<script setup lang="ts">
import SparkMD5 from "spark-md5";
import AsyncTaskController from "./asyncTaskController";
import { getUserInfo } from "@/api/user";

const files = ref<any>([]);
const tableData = ref<any>([]);
const chunkSize = 1024 * 1024 * 5;

const del = (row: any) => {
  console.log(files.value);

  files.value.splice(row.index, 1);
  calcFileStatus();
  console.log(files.value);
};
onMounted(() => {
  getUserInfo();
});

const createChunks = (file: File, chunkSize: number) => {
  const chunks = [];
  for (let i = 0; i < file.size; i += chunkSize) {
    chunks.push(file?.slice(i, i + chunkSize));
  }
  return chunks;
};

const hash = (chunks: any[]) => {
  return new Promise((resolve, reject) => {
    const worker = new Worker(new URL("./hash.ts", import.meta.url), {
      type: "module",
    });
    worker.postMessage({
      chunks,
    });
    worker.onmessage = (e) => {
      const { hash } = e.data;
      if (hash) {
        resolve(hash);
      }
    };
    worker.terminate();
  });
};

const calcFileIndex = () => {
  return files.value.map((item: any, index: number) => {
    item.index = index;
    return item;
  });
};
const calcFileStatus = () => {
  calcFileIndex()
    .filter((e: any) => e.status !== 1 || e.status !== 2)
    .forEach(async (item: any) => {
      if (!(item instanceof File)) {
        return false;
      }
      item = item as File;
      item.status = 0;
      item.percentage = 0;
      const result = createChunks(item, chunkSize);
      const md5 = await hash(result);
      item.status = 1;
      item.md5 = md5;
      item.chunkMap = result;
      item.chunks = result.length;
      files.value.splice(item.index, 1, {
        status: 1,
        md5: item.md5,
        size: item.size,
        name: item.name,
        chunkMap: item.chunkMap,
        chunks: item.chunks,
        index: item.index,
        percentage: item.percentage,
      });
    });
};

const onChange = async (file: FileList) => {
  if (!file) {
    return;
  }

  const fs = Object.values(file);

  fs.forEach((item: any) => {
    files.value.push(item);
  });
  calcFileStatus();
};

watch(
  () => files.value,
  (arr) => {
    tableData.value = arr;
  },
  { deep: true },
);
const getParams = (params: any) => {
  const formData = new FormData();
  for (const key in params) {
    formData.append(key, params[key]);
  }
  return formData;
};

const parallelTask = (
  tasks: Array<any>,
  parallelCount = navigator.hardwareConcurrency,
  item: any,
) => {
  return new Promise((resolve, reject) => {
    if (tasks.length === 0) {
      resolve(0);
      return;
    }

    let nextIndex = 0;
    let finishCount = 0;
    let tryCount = 3;
    function _run() {
      // 运行下一个任务
      const task = tasks[nextIndex];
      task()
        .then(() => {
          finishCount++;
          nextIndex++;

          files.value[item.index].percentage += +(100 / tasks.length).toFixed(
            2,
          );

          // files.value[item.curIndex]
          if (nextIndex < tasks.length) {
            _run();
          } else if (finishCount === tasks.length) {
            resolve(1);
          }
        })
        .catch((err: any) => {
          tryCount++;
          if (tryCount < 3) {
            _run();
          } else {
            ElMessage.error(
              err + item.name + "第" + nextIndex + "片" + "上传失败",
            );
          }
        });
    }
    for (let i = 0; i < parallelCount && i < tasks.length; i++) {
      _run();
    }
  });
};
const tasks = (item: any) => {
  return item.chunkMap.map((e: Blob, index: Number) => {
    return async () => {
      const formData = getParams({
        name: item.name,
        md5: item.md5,
        size: item.size,
        chunks: item.chunks,
        chunk: index,
        file: e,
      });
      await axios.post("http://localhost:9090/file/upload/", formData);
    };
  });
};
// 设置待上传
const awaitUpload = (curIndex: number) => {
  files.value.map((item: any, index: number, arr: any) => {
    if (item.index > curIndex) {
      files.value.splice(item.index, 1, { ...item, status: 5 });
    }
  });
};

const upload = () => {
  if (files.value.some((item: any) => item.status === 0)) {
    ElMessage.error("请等待所有文件计算md5成功");
    return false;
  }
  if (files.value.every((item: any) => item.status === 3)) {
    ElMessage.error("没有可上传的文件");
    return false;
  }
  const uploadFileArr = files.value.filter((e: any) => e.status !== 3);
  let curFile = 0;
  const _runTask = () => {
    const item = uploadFileArr[curFile];
    const task = tasks(item);
    awaitUpload(curFile);
    files.value[item.index].status = 2;
    parallelTask(task, 4, item).then((e) => {
      ElMessage.success("上传完成");
      files.value[item.index].status = 3;
      curFile++;
      if (curFile < uploadFileArr.length) {
        _runTask();
      }
    });
  };
  _runTask();
};
</script>

<style lang="scss" scoped>
.app-container {
  background-color: #fff;
}
</style>
